package com.xgimi.open.skill.datamanager.biz.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.xgimi.gmui.glog.sdk.GlogRecorder;
import com.xgimi.open.skill.datamanager.biz.common.Enum.ExceptionErrorCode;
import com.xgimi.open.skill.datamanager.biz.common.exception.BizAssert;
import com.xgimi.open.skill.datamanager.biz.common.exception.BizException;
import com.xgimi.open.skill.datamanager.biz.common.redis.RedisLockService;
import com.xgimi.open.skill.datamanager.biz.constant.NluConstants;
import com.xgimi.open.skill.datamanager.biz.service.SkillVersionBizService;
import com.xgimi.open.skill.datamanager.biz.service.SlotBizService;
import com.xgimi.open.skill.datamanager.biz.utils.BusinessIdUtil;
import com.xgimi.open.skill.datamanager.biz.utils.RedisKeyBuilder;
import com.xgimi.open.skill.datamanager.domain.entity.*;
import com.xgimi.open.skill.datamanager.domain.service.IntentService;
import com.xgimi.open.skill.datamanager.domain.service.IntentSlotService;
import com.xgimi.open.skill.datamanager.domain.service.SlotService;
import com.xgimi.open.skill.datamanager.domain.service.WordStockService;
import com.xgimi.open.skill.datamanager.service.request.SlotReqDTO;
import com.xgimi.open.skill.datamanager.service.response.SlotPagequeryRespDTO;
import com.xgimi.open.skill.datamanager.service.response.SlotRespDTO;
import com.xgimi.open.skill.datamanager.service.response.page.IPageUtils;
import com.xgimi.open.skill.datamanager.service.response.page.PageResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author coderzpw.zhang
 * @version 1.0
 * @description 描述
 * @date 2023/2/8 14:32
 */
@Slf4j
@Service
public class SlotBizServiceImpl implements SlotBizService {

    private final SlotService slotService;
    private final WordStockService wordStockService;
    private final IntentService intentService;
    private final RedisLockService redisLockService;
    private final SkillVersionBizService skillVersionBizService;
    private final IntentSlotService intentSlotService;

    private final GlogRecorder glogRecorder;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public SlotBizServiceImpl(SlotService slotService,
                              WordStockService wordStockService,
                              IntentService intentService,
                              RedisLockService redisLockService,
                              SkillVersionBizService skillVersionBizService,
                              IntentSlotService intentSlotService,
                              GlogRecorder glogRecorder) {
        this.slotService = slotService;
        this.wordStockService = wordStockService;
        this.intentService = intentService;
        this.redisLockService = redisLockService;
        this.skillVersionBizService = skillVersionBizService;
        this.intentSlotService = intentSlotService;
        this.glogRecorder = glogRecorder;
    }

    @Override
    public Boolean addSlot(SlotReqDTO slotReqDTO) {
        SlotDO slotDO = new SlotDO();
        BeanUtils.copyProperties(slotReqDTO, slotDO);
        slotDO.setCreatedBy(slotReqDTO.getCreatedBy());
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(slotReqDTO.getSkillVid());
        boolean ret;
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            // 1、查询技能版本表中 某个技能版本 是否已有重复的slot，根据slotName判断
            List<SlotDO> list = slotService.querySlotByNameAndVid(slotDO);
            if (list.size() > 0) {
                throw new BizException(ExceptionErrorCode.SOLT_NAME_ALREADY_EXISTS);
            }
            // 2、新增product
            slotDO.setBusinessId(BusinessIdUtil.getSlotUUID());
            ret = slotService.save(slotDO);
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        glogRecorder.recordAdd(slotReqDTO.getCreatedBy(),
                "slot", "addSlot",
                String.valueOf(ret), slotReqDTO);
        return ret;
    }

    @Override
    public List<SlotRespDTO> listByIds(List<Long> ids) {
        List<SlotDO> slotDOS = slotService.listByIds(ids);
        List<SlotRespDTO> result = slotDOS.stream().map(x -> {
            SlotRespDTO DTO = new SlotRespDTO();
            BeanUtils.copyProperties(x, DTO);
            return DTO;
        }).collect(Collectors.toList());
        return result;
    }


    @Override
    public Boolean deleteSlot(SlotReqDTO slotReqDTO) {
        BizAssert.notNull(slotReqDTO.getId(), ExceptionErrorCode.SOLT_ID_IS_NULL);
        // 先根据技能id查询 是否存在该槽位
        SlotDO res = slotService.queryById(slotReqDTO.getId());
        BizAssert.notNull(res, ExceptionErrorCode.SOLT_NOT_EXISTS);
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(res.getSkillVid());
        boolean ret;
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            SlotDO slotDO = new SlotDO();
            BeanUtils.copyProperties(slotReqDTO, slotDO);
            // 若存在意图引用该槽位 则删除失败
            List<IntentSlotDO> intentSlotDOS = intentSlotService.getBySlotId(slotDO.getId());
            if (!CollectionUtils.isEmpty(intentSlotDOS)) {
                throw new BizException(ExceptionErrorCode.REFERENCED_SLOT_CANNOT_DELETE);
            }
            // 删除技能
            ret = slotService.removeById(slotDO.getId());
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        glogRecorder.recordDel(slotReqDTO.getUpdatedBy(),
                "slot", "deleteSlot",
                slotReqDTO.toString());
        return ret;
    }


    @Override
    public Boolean updateSlot(SlotReqDTO slotReqDTO) {
        BizAssert.notNull(slotReqDTO.getId(), ExceptionErrorCode.SOLT_ID_IS_NULL);
        // 先根据槽位id查询 是否存在该槽位
        SlotDO from = slotService.queryById(slotReqDTO.getId());
        BizAssert.notNull(from, ExceptionErrorCode.SOLT_NOT_EXISTS);
        // 若槽位已被意图关联则不让修改
        List<IntentSlotDO> intentSlots = intentSlotService.getBySlotId(from.getId());
        if (!CollectionUtils.isEmpty(intentSlots)) {
            // 存在引用关系，则直接抛出异常，不让修改
            throw new BizException(ExceptionErrorCode.REFERENCED_CAN_NOT_UPDATE);
        }
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(from.getSkillVid());
        boolean ret;
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            SlotDO slotDO = new SlotDO();
            BeanUtils.copyProperties(slotReqDTO, slotDO);
            slotDO.setUpdatedBy(slotReqDTO.getUpdatedBy());
            if (!StringUtils.isEmpty(slotDO.getSlotName())) {
                // 判断当前 技能版本下 是否存在 相同名字的其他槽位，则抛出异常
                List<SlotDO> list = slotService.queryByNameByVidAndNoId(slotDO);
                if (list.size() > 0) {
                    throw new BizException(ExceptionErrorCode.SOLT_NAME_ALREADY_EXISTS);
                }
            }
            // 修改技能
            ret = slotService.updateById(slotDO);
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        SlotDO to = slotService.queryById(slotReqDTO.getId());
        glogRecorder.recordUpdate(slotReqDTO.getUpdatedBy(),
                "slot", "updateSlot",
                String.valueOf(ret), from, to);
        return ret;
    }

    @Override
    public PageResult<SlotPagequeryRespDTO> pageQuery(SlotReqDTO slotReqDTO) {
        skillVersionBizService.checkSkillVersion(slotReqDTO.getSkillVid());
        SlotDO slotDO = new SlotDO();
        BeanUtils.copyProperties(slotReqDTO, slotDO);
        // 构造分页查询的入参
        IPage<SlotDO> page = new Page<>(slotReqDTO.getCurrentPage(), slotReqDTO.getPageSize());
        // 调用domain层分页查询的方法
        IPage<SlotDO> iPage = slotService.pageQuery(page, slotDO);
        List<SlotDO> slotDOS = iPage.getRecords();
        // 若查询没结果，则返回空pageResult
        if (CollectionUtils.isEmpty(slotDOS)) {
            log.info("no such skill!");
            return new PageResult<>(Lists.newArrayList(), IPageUtils.noDataPaginator(slotReqDTO.getPageSize()));
        }
        List<Long> slotIds = slotDOS.stream().map(SlotDO::getId).collect(Collectors.toList());
        // 查询关联这些槽位的所有关联记录
        List<IntentSlotDO> intentSlotDOS = intentSlotService.listBySlotIds(slotIds);
        Map<Long, List<IntentSlotDO>> slotIdIntentIdsMap = intentSlotDOS.stream().collect(Collectors.groupingBy(IntentSlotDO::getSlotId));
        Map<Long, IntentDO> intentDOMap = buildIntentDOMap(intentSlotDOS);
        Map<Long, WordStockDO> wordStockDOMap = buildWordStockDOMap(slotDOS);
        // 若有结果，则将 DO 转成 DTO
        List<SlotPagequeryRespDTO> collect = iPage.getRecords().stream().map(d -> slotDo2SlotPagequeryDTO(d, wordStockDOMap, intentDOMap, slotIdIntentIdsMap)).collect(Collectors.toList());
        return new PageResult<>(collect, IPageUtils.transformToPaginator(iPage));
    }

    public SlotPagequeryRespDTO slotDo2SlotPagequeryDTO(SlotDO slotDO, Map<Long, WordStockDO> wordStockDOMap, Map<Long, IntentDO> intentDOMap, Map<Long, List<IntentSlotDO>> slotIdIntentIdsMap) {
        SlotPagequeryRespDTO slotPagequeryRespDTO = new SlotPagequeryRespDTO();
        BeanUtils.copyProperties(slotDO, slotPagequeryRespDTO);
        // 填充依赖的词库
        WordStockDO wordStockDO = wordStockDOMap.get(slotDO.getRelyonWordStock());
        SlotPagequeryRespDTO.RelyonWordStock relyonWordStock = new SlotPagequeryRespDTO.RelyonWordStock();
        relyonWordStock.setId(wordStockDO.getId());
        relyonWordStock.setWordStockName(wordStockDO.getWordName());
        slotPagequeryRespDTO.setRelyonWordStock(relyonWordStock);
        // 填充被依赖的意图列表
        List<SlotPagequeryRespDTO.RelationIntent> relationIntents = new ArrayList<>();
        List<IntentSlotDO> intentSlotDOS = slotIdIntentIdsMap.get(slotDO.getId());
        if (!CollectionUtils.isEmpty(intentSlotDOS)) {
            List<Long> intentIds = intentSlotDOS.stream().map(IntentSlotDO::getIntentId).collect(Collectors.toList());
            for (Long intentId : intentIds) {
                IntentDO intentDO = intentDOMap.get(intentId);
                SlotPagequeryRespDTO.RelationIntent relationIntent = new SlotPagequeryRespDTO.RelationIntent();
                BeanUtils.copyProperties(intentDO, relationIntent);
                relationIntents.add(relationIntent);
            }
            slotPagequeryRespDTO.setRelationIntents(relationIntents);
        }
        return slotPagequeryRespDTO;
    }

    private Map<Long, IntentDO> buildIntentDOMap(List<IntentSlotDO> intentSlotDOS) {
        Set<Long> intentIdsSet = new HashSet<>();
        for (IntentSlotDO intentSlotDO : intentSlotDOS) {
            intentIdsSet.add(intentSlotDO.getIntentId());
        }
        ArrayList<Long> intentIds = new ArrayList<>(intentIdsSet);
        List<IntentDO> intentDOS = intentService.listByids(intentIds);
        Map<Long, IntentDO> intentDOMap = intentDOS.stream().collect(Collectors.toMap(IntentDO::getId, x -> x));
        return intentDOMap;
    }


    private Map<Long, WordStockDO> buildWordStockDOMap(List<SlotDO> slotDOS) {
        List<Long> relyonWordStocks = slotDOS.stream().map(SlotDO::getRelyonWordStock).collect(Collectors.toList());
        List<WordStockDO> wordStockDOS = wordStockService.listByIds(relyonWordStocks);
        Map<Long, WordStockDO> wordStockDOMap = wordStockDOS.stream().collect(Collectors.toMap(WordStockDO::getId, x -> x));
        return wordStockDOMap;
    }

    @Override
    public List<SlotRespDTO> listBySlotNames(List<String> slotNames, Long skillVid) {
        List<SlotDO> slotDOS = slotService.listBySlotNames(slotNames, skillVid);
        List<SlotRespDTO> result = slotDOS.stream().map(x -> {
            SlotRespDTO DTO = new SlotRespDTO();
            BeanUtils.copyProperties(x, DTO);
            return DTO;
        }).collect(Collectors.toList());
        return result;
    }

    @Override
    public List<SlotRespDTO> listBySkillVid(Long skillVid) {
        List<SlotDO> slotRespDTOS = slotService.listBySkillVid(skillVid);
        // 若有结果，则将 DO 转成 DTO
        List<SlotRespDTO> list = slotRespDTOS.stream().map(d -> {
            SlotRespDTO dto = new SlotRespDTO();
            BeanUtils.copyProperties(d, dto);
            return dto;
        }).collect(Collectors.toList());
        return list;
    }

    /**
     * 加技能锁
     *
     * @param businessId
     */
    private String skillLock(String businessId) {
        String skillUpdateLockKey = RedisKeyBuilder.buildSkillUpdateLockKey(businessId);
        boolean locked = redisLockService.lock(skillUpdateLockKey, NluConstants.NLU_LOCK_EXPIRE_TTS,
                NluConstants.NLU_LOCK_WAIT_TIME_MILLS);
        if (!locked) {
            log.warn("获取技能写锁失败 SkillAppId:{}", businessId);
            throw new BizException(ExceptionErrorCode.SKILL_UPDATE_TRAIN);
        }
        return skillUpdateLockKey;
    }

}
